package what;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.IOException;
import java.io.PrintWriter;
import java.util.LinkedList;

public class WHAT {

	public static void main(String[] args) {
		if (args.length < 1)  {
			System.out.println("Arguments missing: sequence.file windowsize angle (in that order)");
			return;
		}
		try {
			
			// Get sequence from file specified
			File file = new File(args[0]);
			int ch;
			StringBuffer sb = new StringBuffer("");
			FileInputStream fis = null;
			try {
				fis = new FileInputStream(file);
			 	while ((ch = fis.read()) != -1)
			  		sb.append((char) ch);
			 	fis.close();
			} catch (FileNotFoundException e) {
				System.out.println("File not found.");
				e.printStackTrace();
			}
			String seq = sb.toString();
			
			// Get window size, angle & format
			int windowSize = Integer.parseInt(args[1]);
			int angle = Integer.parseInt(args[2]);
			
			// Chop it up into specific sequences
			String[] seqInput = seq.trim().split("\\n");
			LinkedList<String> inSeqRow = new LinkedList<String>();
			if (seqInput[0].trim().matches(">.+") == false) {
				inSeqRow.addFirst(">sample sequence");
			}
			
			for (int i = 0; i < seqInput.length; i++) {
				inSeqRow.addFirst(seqInput[i].trim());
			}
			seqInput = null; //clear seqInput array;

			InSeqFormat alignment = new InSeqFormatFasta(inSeqRow);


			Average average = new Average(alignment.getSeqArray()[0], windowSize, angle);

			float[] testHydro = average.hydrophobicity();
			float[] testAmphi = average.amphipathicity();
			int sizeArray = testHydro.length;

			System.out.println("Hydrophobicity\tAmphipathicity");
			for (int i = 0; i < testHydro.length; i++) {
				System.out.println(testHydro[i]+ "\t" +testAmphi[i]);
			}
			
		} catch (FileNotFoundException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		} catch (IOException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
	}
}

abstract class InSeqFormat {
	protected LinkedList inSeqRow;
	protected byte [][] seqArray;
	protected String [] idArray;

	public void inSeq(LinkedList inSeqRow) {
		this.inSeqRow = inSeqRow;
	}
	
	public byte [][] getSeqArray() {
		if (this.seqArray == null) {
			this.getArray();
		}
		return this.seqArray;
	}

	public String [] getIdArray() {
		if (this.idArray == null) {
			this.getArray();
		}
		return this.idArray;
	}

	protected abstract void getArray();
}

class InSeqFormatFasta extends InSeqFormat {
	public InSeqFormatFasta(LinkedList inSeqRow) {
		super.inSeqRow = inSeqRow;
	}
	
	protected void getArray() {
		LinkedList seqHolder = new LinkedList();
		LinkedList idHolder = new LinkedList();
		LinkedList lineCount = new LinkedList();
		int totalLength = 0;
		int totalLine = 0;
		int seqNum = 0;
		while(super.inSeqRow.isEmpty() != true) {
			String data = super.inSeqRow.removeLast().toString().trim();
			if (data.matches(">.+")) {
				idHolder.addFirst(data);
				++seqNum;
			} else {
				data = data.replaceAll("[^a-zA-Z-]", "");
				totalLength += data.length();
				++totalLine;
				seqHolder.addFirst(data);
			}
			if (data.matches(">.+") && totalLength != 0 || super.inSeqRow.isEmpty()) {
				lineCount.addFirst(totalLine + "\t" + totalLength);
				totalLength = 0;
				totalLine = 0;
			}
		}
		super.idArray = new String[seqNum];
		super.seqArray = new byte [seqNum][];
		for (int i = 0; i < seqNum; i++) {
			super.idArray[i] = idHolder.removeLast().toString();
			String[] temp = lineCount.removeLast().toString().trim().split("\\s+");
                	totalLine = Integer.parseInt(temp[0]);
			totalLength = Integer.parseInt(temp[1]);
		//	System.out.println(totalLine + "\t" + totalLength);
			super.seqArray[i] = new byte [totalLength];
			int seqLength = 0;
			for (int j = 0; j < totalLine; j++) {
				String seq = seqHolder.removeLast().toString().toUpperCase();
			//	System.out.println(seqLength + " " +seq + " " + seq.length());
				System.arraycopy(seq.getBytes(),0,seqArray[i],seqLength,seq.length());
				seqLength += seq.length();
			}
		}
	}
}

class Average {
	private byte [][] seqArray;
	private int windowSize;
	private static String hydroIndex = "ACDEFGHIKLMNPQRSTVWYBJOUXZ";
	private static float[] hydroScore = {1.8f,2.5f,-3.5f,-3.5f,2.8f,-0.4f,-3.2f,4.5f,-3.9f,3.8f,1.9f,-3.5f,
		-1.6f,-3.5f,-4.5f,-0.8f,-0.7f,4.2f,-0.9f,-1.3f,0f,0f,0f,0f,0f,0f};
	private static float[] cosScore = {1f,0.9961f,0.9848f,0.9659f,0.9396f,0.9063f,0.8660f,0.8191f,0.7660f,0.7071f,0.6427f,0.5735f,
		0.5000f,0.4226f,0.3420f,0.2588f,0.1736f,0.0871f,0f,-0.087f,-0.173f,-0.258f,-0.342f,-0.422f,
		-0.500f,-0.573f,-0.642f,-0.707f,-0.766f,-0.819f,-0.866f,-0.906f,-0.939f,-0.965f,-0.984f,-0.996f,
		-1f,-0.996f,-0.984f,-0.965f,-0.939f,-0.906f,-0.866f,-0.819f,-0.766f,-0.707f,-0.642f,-0.573f,
		-0.500f,-0.422f,-0.342f,-0.258f,-0.173f,-0.087f,0f,0.0871f,0.1736f,0.2588f,0.3420f,0.4226f,
		0.500f,0.5735f,0.6427f,0.7071f,0.7660f,0.8191f,0.8660f,0.9063f,0.9396f,0.9659f,0.9848f,0.9961f};
	private int angel;
	
	public Average() {
		
	}

	public Average(byte [] seqArray1) {
		this(seqArray1, 19, 100);
	} 

	public Average(byte [][] seqArray) {
		this(seqArray, 19, 100);

	}

	public Average(byte [] seqArray1, int windowSize) {
		this(seqArray1, windowSize, 100);
	}

	public Average(byte [][] seqArray,int windowSize) {
		this(seqArray, windowSize, 100);
	}

	public Average(byte [] seqArray1, int windowSize, int angel) {
		this.seqArray = new byte [1][];
		this.seqArray[0] = seqArray1;
		this.windowSize = windowSize;
		this.angel = angel;
	}

	public Average(byte [][] seqArray,int windowSize, int angel) {
		this.seqArray = seqArray;
		this.windowSize = windowSize;
		this.angel = angel;
	}

	public void setWindow (int windowSize) {
		this.windowSize = windowSize;
	}

	public void setArray (byte [][] seqArray) {
		this.seqArray = seqArray;
	}
	
	public void setAngel (int angel) {
		this.angel = angel;
	}

	public float[] similarity() {
		float[] scoreRow = new float[this.seqArray[0].length];
		for (int j = 0; j < scoreRow.length; j++) {
			int[] seqColumn = new int[26];
			for (int i = 0; i < seqArray.length; i++) {
				if (this.seqArray[i][j] != 45) {
					seqColumn[this.seqArray[i][j] -65] += 1;
				}
			}
			int sumSqure = 0;;
		 	for (int i = 0; i < seqColumn.length; i++) {
				sumSqure += Math.pow(seqColumn[i],2);
			}
			scoreRow[j]= (float)Math.sqrt(sumSqure)/seqArray.length*100;
		}
		return this.averageArray(scoreRow);
	}
	
	
	public float[] amphipathicity() {
		int armSize = (windowSize - 1) / 2;
		float[] scoreRow = new float[this.seqArray[0].length];
		float hydroSum;
		int countScore;
		for (int j = 0; j < scoreRow.length; j++) {		
			hydroSum = 0;
			countScore= 0;

			float subSumX;
			float subSumY;
			int subCount;
			for (int i = 0; i < this.seqArray.length; i++) {
				if (this.seqArray[i][j] != 45) {
					subSumX = 0;
					subSumY = 0;
					subCount = 0;
					int firstPosition = j;
					int firstCount = armSize;
					while(firstCount > 0 && firstPosition > 0){
						firstPosition--; 
						if (this.seqArray[i][firstPosition] != 45) {
							firstCount--;
						}
					}
					int lastPosition = j;
					int lastCount = armSize;
					while(lastCount > 0 && lastPosition < this.seqArray[0].length-1){
						lastPosition++;
						if (this.seqArray[i][lastPosition] != 45) {
							lastCount--;
						}
					}
					int firstToCenter = firstCount-armSize;
					for (int k = firstPosition; k<= lastPosition; k++) {
						if (this.seqArray[i][k] != 45) {
						//	float radianThis = (float)(firstToCenter*this.angel+45)/57.3f;
							int index = this.hydroIndex.indexOf((char)this.seqArray[i][k]);
						//	float angelEffectX = (float)Math.cos(radianThis);
						//	float angelEffectY = (float)Math.sin(radianThis);
							int IndexCosX = Math.abs(((firstToCenter*this.angel-45)/5)%72);
							int IndexCosY = Math.abs(((firstToCenter*this.angel+45)/5)%72);;
							float angelEffectX = cosScore[IndexCosX];
							float angelEffectY = cosScore[IndexCosY];
							if (index >= 0) {
								subSumX += this.hydroScore[index]*angelEffectX;
								subSumY += this.hydroScore[index]*angelEffectY;
								subCount ++;								
							}
							
							firstToCenter++;
						//	System.out.print((char)this.seqArray[i][k]);
						}
					//	System.out.print((char)this.seqArray[i][k]);
					}
					hydroSum += Math.sqrt((subSumX*subSumX)+(subSumY*subSumY))/subCount;
					countScore ++;
				//	System.out.println( subSumX + " " + subSumY+ " " + subCount + " ");
				//	System.out.println(firstPosition+" "+lastPosition +" "+ i);
				}
			}
			scoreRow[j] = hydroSum/countScore;
		//	System.out.print(countScore+ " ");
			
		}
			
		return scoreRow;

	}
	
	public float[] hydrophobicity2() {
		int armSize = (windowSize - 1) / 2;
		float[] scoreRow = new float[this.seqArray[0].length];
		for (int j = 0; j < scoreRow.length; j++) {		
			float hydroSum =0;
			int countScore= 0;
			for (int i = 0; i < this.seqArray.length; i++) {
				if (this.seqArray[i][j] != 45) {
					int firstPosition = j;
					int firstCount = armSize;
					while(firstCount > 0 && firstPosition > 0){
						firstPosition--; 
						if (this.seqArray[i][firstPosition] != 45) {
							firstCount--;
						}

					}
					int lastPosition = j;
					int lastCount = armSize;
					while(lastCount > 0 && lastPosition < this.seqArray[0].length-1){
						lastPosition++;
						if (this.seqArray[i][lastPosition] != 45) {
							lastCount--;
						}
					}
				//	System.out.print(lastPosition - firstPosition + " ");
					for (int k = firstPosition; k<= lastPosition; k++) {
						if (this.seqArray[i][k] != 45) {
							int index = this.hydroIndex.indexOf((char)this.seqArray[i][k]);
							if (index >=0 ) {
								hydroSum += this.hydroScore[index];
								countScore ++;
							}
						//	System.out.print((char)this.seqArray[i][k]);
						}
					//	System.out.print((char)this.seqArray[i][k]);
					}
				//	System.out.println(firstPosition+" "+lastPosition +" "+ i);
				}
			}
			
			scoreRow[j] = hydroSum/countScore;
		//	System.out.print(countScore+ " ");
			
		}
		return scoreRow;

	}
	
	public float[] hydrophobicity() {
		float[] scoreRow = new float[this.seqArray[0].length];
		for (int j = 0; j < scoreRow.length; j++) {
			float hydroSum =-1;;
			int seqCount = 0;
			for (int i = 0; i < this.seqArray.length; i++) {
				if (this.seqArray[i][j] != 45) {
					int index = this.hydroIndex.indexOf((char)this.seqArray[i][j]);
					if (index >=0 ) {
						hydroSum += this.hydroScore[index];
						seqCount++;
					} 					
				//	System.out.print(this.hydroScore[index] + " " + (char)this.seqArray[i][j] + " ");

				}
			}
			scoreRow[j]= hydroSum/seqCount;
		}
		return this.averageArray(scoreRow);
	//	return scoreRow;
	}

	private float[] averageArray(float[] inArray) {
		int armSize = (windowSize - 1) / 2;
		float [] averArray = new float [inArray.length];
		for (int i = 0; i < inArray.length; i++) {
			int firstScore = (i > armSize ? i - armSize : 0) ;
			int lastScore = (inArray.length - i > armSize ? i + armSize  : inArray.length - 1);
			float sumAver = 0;
			for (int j = firstScore; j <= lastScore; j++) {
				sumAver += inArray[j];
			}
			averArray[i] = sumAver / (lastScore - firstScore + 1);
		//	System.out.print(lastScore - firstScore + 1 + " ");
		}
		return averArray;
	}
}